//
//  node.h
//  cobalt
//
//  Created by Nicolas Godbout on 2013-09-01.
//  Copyright (c) 2013 Nicolas Godbout. All rights reserved.
//

#ifndef __cobalt__node__
#define __cobalt__node__

#include <string>

#include "gtest/gtest_prod.h"

namespace rdf {

    class NamedNode;
    class BlankNode;
    class Literal;

    class RDFNode {
    public:
        const char* interfaceName() const;
        std::string     toNT() const;
        
        // constructors
        RDFNode();
        RDFNode(RDFNode const&);
        RDFNode(RDFNode&&);
        RDFNode& operator=(RDFNode const&);
        RDFNode& operator=(RDFNode&&);
        ~RDFNode();
        
        // conditional casting to sub-types
        operator NamedNode const& () const;
        operator BlankNode const& () const;
        operator Literal const& () const;
        explicit operator bool() const;
        
        bool operator==(RDFNode const& other) const;
        bool operator!=(RDFNode const& other) const;
        bool operator< (RDFNode const& other) const;
        
        // semi-private constructor
        RDFNode (struct NodeRecord&&);
        friend class Triple;
        
        FRIEND_TEST(NodeTest, PointerEquality);
    protected:
        NodeRecord*     node;
    };


    class NamedNode : public RDFNode {
    public:
        const char* interfaceName() const;
        std::string toNT() const;
        
        bool operator==(NamedNode const&) const;
        bool operator< (NamedNode const&) const;
    };

    class BlankNode : public RDFNode {
    public:
        const char* interfaceName() const;
        std::string toNT() const;
        
        bool operator==(BlankNode const&) const;
        bool operator< (BlankNode const&) const;
    };

    class Literal : public RDFNode {
    public:
        const char* interfaceName() const;
        std::string toNT() const;
        
        const char* language() const;
        NamedNode const& datatype() const;
        
        bool operator==(Literal const&) const;
        bool operator< (Literal const&) const;
    };

    /** Private implementation **/

    extern const char* str_BlankNode;
    extern const char* str_NamedNode;
    extern const char* str_Literal;
    extern const char* str_LangString;
    
    struct NodeRecord {
        uint32_t refCount;
        struct Flags {
            unsigned int padding : 32;
        } flags;
        const char* interfaceName;
        const char* nominalValue;
        union {
            void* referent;
            const char* lang;
            NamedNode datatype;
        };
        
        NodeRecord(NodeRecord&&) noexcept;
        
        // ## TODO: all c-strings are copied
        explicit NodeRecord(const char* interfaceName,
                   const char* nominalValue) noexcept;
        
        explicit NodeRecord(const char* interfaceName,
                   const char* nominalValue,
                   const char* lang) noexcept;
        
        explicit NodeRecord(const char* interfaceName,
                   const char* nominalValue,
                   NamedNode const& datatype) noexcept;
        ~NodeRecord() noexcept;
        
        inline
        char *strdup (const char *s) {
            char *d = new char [ strlen(s)+1 ];
            strcpy (d,s);
            return d;
        }

    };
        
    inline
    NodeRecord::NodeRecord(NodeRecord&& other) noexcept
    : refCount(other.refCount), flags(other.flags)
    , interfaceName(other.interfaceName)
    , nominalValue(strdup(other.nominalValue))
    , lang(nullptr) {
        if ( interfaceName == str_Literal ) {
            datatype = other.datatype;
        } else if ( interfaceName == str_LangString)
            lang = other.lang;
        other.interfaceName = nullptr;
    }
    
    inline
    NodeRecord::NodeRecord(const char* interfaceName,
                           const char* nominalValue) noexcept
    : refCount(1), flags({0})
    , interfaceName(interfaceName)
    , nominalValue(strdup(nominalValue))
    , referent(nullptr) {}
    
    inline
    NodeRecord::NodeRecord(const char* interfaceName,
                           const char* nominalValue,
                           const char* lang) noexcept
    : refCount(1), flags({0})
    , interfaceName(interfaceName)
    , nominalValue(strdup(nominalValue))
    , lang(strdup(lang)) {}
    
    inline
    NodeRecord::NodeRecord(const char* interfaceName,
                           const char* nominalValue,
                           NamedNode const& datatype) noexcept
    : refCount(1), flags({0})
    , interfaceName(interfaceName)
    , nominalValue(strdup(nominalValue))
    , datatype(datatype) {}
    
    inline
    NodeRecord::~NodeRecord() noexcept {
        if ( interfaceName ) {
            delete nominalValue;
            if ( interfaceName == str_Literal )
                datatype.~NamedNode();
            else if ( interfaceName == str_LangString )
                delete[] lang;
        }
    }
    
    inline
    RDFNode::RDFNode()
    : node(nullptr) {}

    inline
    RDFNode::RDFNode(RDFNode const& other)
    : node(other.node) {
        if ( node ) node->refCount += 1;
    }

    inline
    RDFNode::RDFNode(RDFNode&& other) {
        node = other.node;
        other.node = nullptr;
    }

    inline RDFNode&
    RDFNode::operator= (RDFNode const& other) {
        if ( node && --(node->refCount) == 0 )
            delete node;
        node = other.node;
        if ( node )
            node->refCount += 1;
        return *this;
    }

    inline RDFNode&
    RDFNode::operator= (RDFNode&& other) {
        NodeRecord *tmp = node;
        node = other.node;
        other.node = tmp;
        return *this;
    }
    
    inline
    RDFNode::RDFNode(NodeRecord&& rec)
    : node(new NodeRecord(std::move(rec))) {}
    
    inline
    RDFNode::~RDFNode() {
        if ( node && --(node->refCount) == 0 )
            delete node;
    }
    
    inline
    RDFNode::operator BlankNode const& () const {
        if ( node && node->interfaceName == str_BlankNode )
            return static_cast<BlankNode const&>(*this);
        else
            throw; // bad_cast
    }

    inline
    RDFNode::operator NamedNode const& () const {
        if ( node && node->interfaceName == str_NamedNode )
            return static_cast<NamedNode const&>(*this);
        else
            throw; // bad_cast
    }

    inline
    RDFNode::operator Literal const& () const {
        if ( node && (node->interfaceName == str_Literal || node->interfaceName == str_LangString) )
            return static_cast<Literal const&>(*this);
        else
            throw; // bad_cast
    }

    inline
    RDFNode::operator bool() const {
        return node != nullptr;
    }

    inline const char*
    RDFNode::interfaceName() const {
        if ( node )
            return node->interfaceName;
        else
            return nullptr;
    }

    inline const char*
    NamedNode::interfaceName() const {
        return str_NamedNode;
    }

    inline const char*
    BlankNode::interfaceName() const {
        return str_BlankNode;
    }

    inline const char*
    Literal::interfaceName() const {
        return str_Literal;
    }
    
    inline bool
    RDFNode::operator!=(RDFNode const& other) const {
        return ! (*this == other);
    }
    
    inline bool
    BlankNode::operator==(BlankNode const& other) const {
        return std::strcmp(node->nominalValue, other.node->nominalValue) == 0;
    }
    
    inline bool
    NamedNode::operator==(NamedNode const& other) const {
        return std::strcmp(node->nominalValue, other.node->nominalValue) == 0;
    }
    
    inline bool
    BlankNode::operator<(BlankNode const& other) const {
        return std::strcmp(node->nominalValue, other.node->nominalValue) < 0;
    }
    
    inline bool
    NamedNode::operator<(NamedNode const& other) const {
        return std::strcmp(node->nominalValue, other.node->nominalValue) < 0;
    }
    
} // namespace rdf
    
#endif /* defined(__cobalt__node__) */
